from IPython.display import Image
Image('./photos/2016_03_22_Gen_II_Field_Plate_Assembly.jpg')
Here a procedure and some notes regarding the assembly and alignment the Gen II electric field plates.
First the East field plate (which is the fixed plate) is assembled.
The gold-coated copper guard ring is placed face down on some aluminum foil in the laminar flow hood.
A .005" thick piece of kapton is laid in the copper guard ring groove - this kapton piece has tabs which hug the field plate. This serves to keep the edge of the field plate which may be conductive from making electrical contact with the guard ring. Although we plan to always have the field plate and guard ring tied to the same voltage input, we have decided to keep the field plate and the guard ring electrically isolated as a design criterion.
A .030" thick piece of "virgin" viton (obtained from EqualSeal, and manufactured into shape with the waterjet) is laid on top of the kapton sheet. This viton sheet serves as a squishy layer that is used to distribute force from the fairly bent and uneven guard rings so that the clamping mechanism for the field plates does not significantly bend the field plates (we previously saw the Gen I field plate bend get an order of magnitude worse when used with guard rings with a 1 mm bend across the piece with no squishy layer).
Another .005" thick piece of kapton is laid on top of the viton so that the field plate ITO surface does not make contact with the viton, which outgasses more than other materials like the kapton. This kapton piece does not have tabs and is a purely 2d piece.
Two PEEK parts, which serve as the bottom half of electrical connection clamps are placed into grooves on the bottom of the guard ring. These both have a small piece of .005" thick indium. I preferred to use indium which was already on the PEEK part over new indium because the existing indium was sticking well to the PEEK, and adding new indium tabs often results in difficulty making sure that the indium is not shorting to the guard rings, and not moving around during the positioning of other parts.
The Gen II field plate was removed from its bubble wrap packaging and paper packaging, and then was moved to the laminar flow hood where the optics paper packaging was removed. We then used a volt-meter to check which side of the field plate was conductive so that we would orient the field plates appropriately in the mount. The volt meter is not a "clean tool", but the volt meter probe tips were cleaned with iso, and the probing was performed in a non-critical region of the plates - along the edge. The AR coated side is pointed to by a designating arrow on the side of the field plates, and the ITO coating is on the other side.
The Gen II plate is placed in the guard ring groove on top of the kapton sandwiched viton, and on top of the indium-topped PEEK clamp bottoms. It usually takes some finnagling in order to get the wrap-around tabs to hug the field plate appropriately and not get crushed under the plate. Interestingly this time this work was not so difficult - it seems that the outer dimensions of the Gen II field plates are a bit smaller than those of the Gen I field plates and so the insertion of the plates during the step goes much more smoothly. After this step, the viton is sometimes sticking out beyond the extent of the guard rings and I try to push the viton so that is it hid behind the guard ring using a small flat head screwdriver.
Next, a kapton coated copper wire with a stripped end is fed into a small cavity between the indium sitting on the PEEK clamp, and the corner of the field plate ITO surface. Once in place, the top part of the clamp is tightened using an aluminum screw such that the indium is pressed against the wire and the ITO surface to make electrical contact. While doing so, it is important to inspect the wire to make sure that it doesn't slip out from on the indium foil while tightening. It is also important to ensure that the viton or kapton sheets are not squeezed by the clamp - often this will be the case, and some finagling is required to ensure that this doesn't happen. This is repeated for the other electrical connection. The electrical connections to the guard rings are made by clamping a kapton coated wire to the guard ring with the head of a small brass screw that is inserted into a blind tapped hole near where the field plate connection was made on the corner of the plate.
Next, the AR coated side of the field plate is dusted off using some compressed CO2 gas, and then a piece of .005" thick kapton is placed around the perimeter of the glass which serves as a buffer between the backplate holder and the glass. Then the backplate is placed on top of the kapton sheet so that the field plate holder sandwich is completed.
Then tapped peek screws are placed around the perimeter of the field plate sandwich to hold it together. We inserted screws in about 1/3 of the hole positions, and tightened these screws in a star pattern as if tightening a vacuum flange, but not as tightly.
All electrical connections were checked to ensure that the field plate leads were electrically connected to the ITO surface, and not to the guard rings, and to ensure that the guard ring leads were electrically connected to the guard rings, and not to the ITO surface.
The east field plate is then mounted vertically on the field plate breadboard by connecting 4 1/4-20 peak screws to the west field plate to two L-brackets that are fixed to the breadboard. We then inspected the east field plate under the scrutiny of a bright flashlight and looked for smudge marks/scratches/dust. It appears that there was a significant amount of dust on the field plates - surprising given that we had just taken the field plates out of their packaging and had done so in a laminar flow hood. We attempted to remove some of this dust by spraying some CO2 compressed air on the plate. Other than the dust, there appeared to be minimal imperfections.
The west field plate was assembled in the same way as the east field plate.
The three brass 100 threads/inch micrometer screws with a PEEK thread holders were placed in three corners of the east field plate backplate. These positions were (upstream, up), (upstream, down), and (downstream, down). These micrometer screws set the separation distance of the plates, and allow fine adjustment of the tilt of the plates. The insertion of these screws into their respective slots is touch, and usually some controlled 'hammering' of these screws is required to get them in place.
The west field plate is then placed so that it is leaning up against these three micrometer screws.
Then a set of phosphor bronze springs is attached at locations near each of the micrometer screws that hold the two plates together.
After all of the screws are in place, ceramic balls are placed between the ends of the micrometer screws and the west field plate backplate that pushes against it. These ceramic balls can be difficult to get into place and it is useful to use a screwdriver to guide the balls into place. These balls sometimes pop out and get lost while working on the assembly, or when inserting them to begin with, so it is wise to have back-ups of these ceramic balls.
The kapton coated copper wires which are connected to the ITO side of the two bottom corners of each of the two field plates, and the wires which are connected to the guard ring at positions near each of the field plate connections, then need to be connected to terminal blocks that are located on the field plate breadboard for providing voltage to the plates. The electric connections are made by copper clamps, and peak screws are used for strain relief of the wires. After connecting the wires to the terminal blocks, we ensured that the corresponding terminals on the north and south terminal blocks were connected together, and that the non-corresponding terminals on the north and south terminal blocks were not connected. These are connected using the following logic from north to south:
The alignment guides are placed on the north side of the the field plate assembly. These are aluminum plates which extend inward almost as far as the east/west grooves in the guard rings, which have rules marks made on them along the y direction, made with a diamond tipped scribe, intended for use when aligning the theodolite with the beam line that is defined by the field plate guard ring grooves.
The fixed collimators were inspected. Previously I had spent some time carefully aligning the fixed collimators to a 24mm square collimator geometry. The titanium blade positions are set by moving the blades along grooves and tightening the blades in place. Since this procedure is performed by hand, the precision of alignment is not much better than 1 mm. There are two fixed collimators, and we measured the collimator spacing along 3 positions for each of the two collimator axes for the two collimators. The region that the molecules will be admitted to correspond roughly to the minimum of the dimensions measured. The average separation along the two dimensions, at the different points measured of the minimum distance between the collimators was about 23.6 mm, and the points were distributed in a range of about .2 mm. This is a bit smaller than the target, but smaller is preferred over bigger (so that we don't run into our molecule geometry contraints), and the size is within our tolerance. So, we did not change the alignment of the collimators.
We placed the collimator on the south side of the field plate assembly and fixed it in place using two peek screws attached to the east field plate backplate. It is also supported by slightly-less-than-fingertight peak screws on the west field plate backplate which still needs to be able to move independently of the east field plate for alignment purposes. There is some freedom in the z position of the fixed collimator, which was first positioned by using calliper measurements given the known geometry of the collimators and the field plate assembly. This was then improved on by "eyeballing" the extent to which the collimator looked aligned when looking down the beamline. A small adjustment was made based on that.
We placed the field plate breadboard with the field plates in the interaction region and screwed the breadboard into the breadboard supports on the base of the chamber using 8 1/4-20 vented brass screws. Then, the kapton coated copper wires that enter into the interaction region through an electrical feedthrough were then identified as corresponding to a given BNC input on the breakout panel outside of the chamber using a multimeter. These wires were then clamped in place to the terminal block on the north end, and the tips of these wires were twisted with other wires which served as jumpers to the terminal block on the south end.
We then performed a series of white light interferometry measurements to measure and correct the field plate spacing and alignment to the target of 45 mm. These measurements and the data are described below.
We then performed theodolite measurements of the alignment of the fixed collimator with respect to the beam axis as specified by the positions of the grooves in the guard rings on the north and south sides of the field plate assembly. According to Zack, The z position of the center of the collimator blades is $z=.4\pm.2$ mm, and the y position of the center of the collimator blades is $y=.0\pm.2$ mm. This is within the safety factor built into the design to ensure that no molecules will hit the field plates or the grooves in the guard rings assuming purely ballistic trajectories.
We then performed electrical tests with the field plates to ensure that they could withstand the full voltage difference that we intend to apply to them.
%load_ext secnum
%secnum
# my own code
import sys
sys.path.insert(0, './code')
sys.path.insert(0, '../DatabaseAccess/code')
sys.path.insert(0, '../Statistics/code')
from interferometer_data_manipulation import InterferometerData, InterferometerMap
from statfunctions import (PolynomialFit1D, KernelEstimator1D,
weighted_mean, regression_plot, correlation_coefficient,
scientific_string, normal_n_sigma_to_p)
from database_access import DatabaseAccess
# standard imports
%matplotlib inline
from datetime import datetime
import time
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import axes3d
import seaborn as sns
import datetime, time
sns.set_style('white')
sns.set_context('poster')
from IPython.display import Image
Here is a photograph of the MIRG interferometer mounted on the 2D tranlation stage, as viewed through the electric field plates that are being remotely probed.
Image('./photos/2016_03_22_interferometer.jpg')
Here is a schematic of the MIRG interferometer. The principle of this measurement scheme relies on a white-light source, and the precision of the measurement is set by the coherence length of the light. When the separation between the two interferometer arms matches the separation between the target surfaces, interference can be seen on the observation port of the interferometer. We detect this interference via fringes imaged on a camera that are present due to a slight misalignment of the interferometer. We extract a signal by looking at the 2D power spectrum of the of the image and integrating over a region corresponding to the periodicity of the fringes.
Image('./photos/MIRG_Schematic.jpg')
Here is a screen shot of the LabVIEW interface that is used to aquire the data. On the upper right, the CCD image is showing the characteristic fringes of the misaligned interferometer with arm length difference equal to the field plate separation. The bottom right shows the 2d power spectrum - the region bounded by the yellow rectangle is the region of the power spectrum that contains the fringe signal and excludes the incoherent signal and is intrated to obtain the fringe contrast. The arm length separation is varied using another translation stage, and that translation stage position is plotted vs fringe contrast on the bottom left.
Image('./photos/Zaber_Fringe_Constrast_Scan.jpg')
First, we inserted the field plates into the interaction region, and then we performed interferometer scans on each of the three corners of the plates that correspond to where each of the three micrometer screws live. Then after measuring the deviation of the field plate separation relative to ideal, we make iterative corrections to the field plate separation. The pitch of the micrometer screws is 100 threads per inch, so each micrometer screw turn corresponds to 254 $\mu$m. Below is a graph of the corrections made vs. iteration number. The iterations are stopped once the separation is within .1 turn (25 $\mu$m).
Turns = [[-8.8, -9.0, -8.5],
[.69, .97, .60],
[.116, .25, .07],
[.04, .048, .048]]
Locations = ['upstream, up',
'upstream, down',
'downstream, down']
for i in range(len(Turns[0])):
plt.plot(np.abs(np.array(Turns)[:,i]),'-o', label=Locations[i])
plt.yscale('log')
plt.xlabel('iteration number')
plt.ylabel('|number of turns|')
plt.title('convergence of field plate separation')
_=plt.legend(loc='best')
After performing the iterative course corrections to the field plate separation on each of the three micrometer corners, we proceed with creating a more detailed map of the field plate separation (a dimension along $z$), along each of the $x$ and $y$ dimensions in the interaction region. The $x$ and $y$ axis offsets are calibrated by positioning the interferometer laser in the center of screw holes on the field plate assembly, allowing a precision of 1-2 mm for those offsets. The absolute separation is determined by first performing a precise calibration scan on the zero-offset peak, enabling absolute positions to be extracted with a precision of a few $\mu$m, limited by the calibration of the translation stage, and limited by drift in components that make up the interferometer over time.
Here is the first map that was taken:
path = './data/2016-03-45mmFPSpacing/2016_03_22_Gen_II_Field_Plate_Assembly_with_Gen_II_Plates-Interferometer Scan 3 22 2016.txt'
title = 'Gen II Field Plate Alignment Scan 1'
scan_map = InterferometerMap.create_from_txt(path,
null_peak_position=50494.6,
setpoint=45000)
# there is a problem with scan # 48, and it isn't being removed by my automatic filters, so remove it by hand, and refit
scan_map.scans.pop(48)
scan_map.create_map(4, True, remove_outliers=True, renormalize_error=True)
Here are some example gaussian fits to the data. This is just check to ensure that my fitting scheme is working appropriately:
sns.set_context('poster')
fig = scan_map.plot(max_line_length=170, h_angle=60, v_angle=40)
fig.savefig('./outputs/interferometer scan results - ' + title + '.pdf')
This looks pretty bent, about as bent as that which we used in Gen I, but about a factor of 2 worse relative to what I was seeing with the Gen I plates with the viton buffer (although that was a piece of viton of different composition).
I tried loosening the screws (that hold the field plates in place) as far as I was comfortable with and then took another scan. It appears that that action induced a larger first order gradient in the separation, but did not affect the bend:
files = ['./data/2016-03-45mmFPSpacing/2016_03_22_Gen_II_Field_Plate_Assembly_with_Gen_II_Plates_Scan_5-Interferometer Scan 3 22 2016.txt',
'./data/2016-03-45mmFPSpacing/2016_03_22_Gen_II_Field_Plate_Assembly_with_Gen_II_Plates_Scan_5-Interferometer Scan 3 23 2016.txt']
title = 'Gen II Field Plate Alignment Scan 2'
scan_map = InterferometerMap.create_from_txt(files,
null_peak_position=50501,
setpoint=45000, order=5)
fig = scan_map.plot(max_line_length=170, h_angle=60, v_angle=40)
fig.savefig('./outputs/interferometer scan results - ' + title + '.pdf')
Since the tightness of the screws did not affect the bend, I tightened the screws again (since I am more comfortable holding the plates more firmly), and am taking a short map scan so that I can remove the first order gradients in a subsequent iteration step. This may take a few iteration steps.
files = ['./data/2016-03-45mmFPSpacing/2016_03_23_Gen_II_Field_Plate_Assembly_with_Gen_II_Plates_Scan_7-Interferometer Scan 3 23 2016.txt',
]
title = 'Gen II Field Plate Alignment Scan 3'
scan_map = InterferometerMap.create_from_txt(files,
null_peak_position=50500.3,
setpoint=45000, order=3)
fig = scan_map.plot(max_line_length=170, h_angle=60, v_angle=40)
fig.savefig('./outputs/interferometer scan results - ' + title + '.pdf')
Here is a function to evaluate the number of turns I need to perform as corrections to each micrometer screw to elliminate the first order gradients from the field plate separation surface.
def corrections_to_make(scan_map, micrometer_positions=[(-24.0,-11.43),
(-24.0,+11.43),
(+24.0,+11.43)], pitch=254.0):
"""
return the number of turns needed at each of the micrometer_positions assuming a given screw pitch
in order to remove the offset and the first order gradients in the field plate separation.
"""
table = scan_map.map.get_coefficients()
offset = float(table[table['names'] == 'a_{}']['value'])
x_grad = float(table[table['names'] == 'a_{$x$}']['value'])
y_grad = float(table[table['names'] == 'a_{$y$}']['value'])
z = [(-(offset + x_grad * pos[0] + y_grad * pos[1]))/pitch
for pos in micrometer_positions]
return z
Here are the corrections that I need to make based on this map. Positive means that I need to increase the field plate separation, by turning the micrometer screws clockwise.
corrections_to_make(scan_map)
After having performed one short scan and correction, I will perform another iteration. Here is another short scan:
files = ['./data/2016-03-45mmFPSpacing/2016_03_23_Gen_II_Field_Plate_Assembly_with_Gen_II_Plates_Scan_8-Interferometer Scan 3 23 2016.txt',
]
title = 'Gen II Field Plate Alignment Scan 4'
scan_map = InterferometerMap.create_from_txt(files,
null_peak_position=50500.3,
setpoint=45000, order=3)
fig = scan_map.plot(max_line_length=170, h_angle=60, v_angle=40)
fig.savefig('./outputs/interferometer scan results - ' + title + '.pdf')
Here are the corrections that are needed to be made to remove the first order gradients. You can see that the largest required correction has been reduced by about a factor of 4 relative to the previous iteration. Once we are below about .1 turns, it is difficult to make the corrections accurately by hand.
corrections_to_make(scan_map)
Now that the required corrections to remove the offset and first order gradients are less than .1, the corrections become more difficult to do. So at this point, I decide that the current field plate alignment is good enough and I will move on to doing some final scans for the future record. First I will do a 2d scan, and then a 1d scan. I will save the extracted separation map to tab delimited text files for easy future plotting.
files = ['./data/2016-03-45mmFPSpacing/2016_03_23_Gen_II_Field_Plate_Assembly_with_Gen_II_Plates_Scan_9-Interferometer Scan 3 23 2016.txt',
]
title = 'Gen II Field Plate Alignment Scan 5'
scan_map_2d_1 = InterferometerMap.create_from_txt(files,
null_peak_position=50500.3,
setpoint=45000, order=5, max_sum=False)
Since this is a final scan, lets take closer look at the quality of the gaussian fits: They generally look pretty good. Data errors were estimated crudely using a sliding window standard deviation on the input data, which causes the fit confidence intervals to narrow near the base and get larger at the peak.
regrs = [scan.regr for scan in scan_map_2d_1.scans[:5]]
regression_plot(regrs, legend=True, chi2=True)
_=plt.legend(loc='center left', bbox_to_anchor=(1,.5))
Here are the $\chi^2$ values for the above fits, which are reasonably consistent with 1 (especially given the crudeness of the method needed to estimate the uncertainties)
Here is the interferometer constrast linewidth from the aggregate data:
scan_map_2d_1.lineshape.get_coefficients().query("names=='s'")
Here is the map itself that we have extracted
fig = scan_map_2d_1.plot(max_line_length=170, h_angle=60, v_angle=40)
fig.savefig('./outputs/interferometer scan results - ' + title + '.pdf')
scan_map_2d_1.save_to_txt(name='2016-03-23-2D_Gen_II_Field_Plate_Separation_Final_Interferometer_Scan_1.txt')
Here are the residuals of the map plot, which suggests that the fit is pretty good and we are neither under nor over-fitting. However, $\chi^2>1$ suggesting additional sources of noise other than that included in the gaussian fits.
scan_map_2d_1.map.plot(residuals=True, errorbars=True)
print('chi2 is larger than 1: ' + scan_map_2d_1.map.get_chi2(string=True, style='plain')
+ "\nthis is suggestive of systematic errors adding additional noise over that from the gaussian fits."
+ "\nas a quick fix, we renormalize all fit errors by chi2 to attempt to include this additional noise"
+ "\nbut this is not a complete solution to the problem")
a histogram of the residuals (with renormalized errors) suggests the presence of tails that are widening the distribution compared to the expected gaussian (shown with blue bands).
scan_map_2d_1.map.histogram()
Here are the most statistically significant polynomial fit coefficients
scan_map_2d_1.map.get_coefficients().sort_values(by='significance', ascending=False).head()
Note that it looks like the bend in the field plates is larger when a higher order polynomial fit is applied. All previous measurements supported a $\sim 7\times10^{-2}\mu \mathrm{m}/\mathrm{cm}^2$ curvature, but this map suggests a $\sim 9\times10^{-2}\mu\mathrm{m}/\mathrm{cm}^2$ curvature without having changed anything in the set-up.
I will saved this raw data to this text file in case someone else wants to use it later.
Here is a subsequent 1D map taken along the beam line with finer spacing.
files = ['./data/2016-03-45mmFPSpacing/2016_03_23_Gen_II_Field_Plate_Assembly_with_Gen_II_Plates_Scan_10-Interferometer Scan 3 23 2016.txt',
]
title = 'Gen II Field Plate Alignment Scan 6'
scan_map_1d_1 = InterferometerMap.create_from_txt(files,
null_peak_position=50500.3,
setpoint=45000, order=6, title='1d scan 1')
scan_map_1d_1.save_to_txt(name='2016-03-23-1D_Gen_II_Field_Plate_Separation_Final_Interferometer_Scan_2.txt')
Here is a repeat of that 1D measurement that I took in order to gauge repeatability of this measurement
files = ['./data/2016-03-45mmFPSpacing/2016_03_23_Gen_II_Field_Plate_Assembly_with_Gen_II_Plates_Scan_11-Interferometer Scan 3 23 2016.txt',
]
title = 'Gen II Field Plate Alignment Scan 7'
scan_map_1d_2 = InterferometerMap.create_from_txt(files,
null_peak_position=50500.3,
setpoint=45000, order=6, title='1d scan 2')
scan_map_1d_2.save_to_txt(name='2016-03-23-1D_Gen_II_Field_Plate_Separation_Final_Interferometer_Scan_3.txt')
Here is a second 2d map that I took to gauge repeatability of the measurement
files = ['./data/2016-03-45mmFPSpacing/2016_03_23_Gen_II_Field_Plate_Assembly_with_Gen_II_Plates_Scan_12-Interferometer Scan 3 23 2016.txt',
]
title = 'Gen II Field Plate Alignment Scan 8'
scan_map_2d_2 = InterferometerMap.create_from_txt(files,
null_peak_position=50500.3,
setpoint=45000, order=5, max_sum=False)
fig = scan_map_2d_2.plot(max_line_length=170, h_angle=60, v_angle=40)
fig.savefig('./outputs/interferometer scan results - ' + title + '.pdf')
scan_map_2d_2.save_to_txt(name='2016-03-23-2D_Gen_II_Field_Plate_Separation_Final_Interferometer_Scan_4.txt')
I took 4 measurements - 2 in 2d and 2 in 1d because i noticed a discrepancy in the offset between the first 2d scan and the first 1d scan. I wanted to check the repeatability of the measurements. Ultimately if looks like there is some sort of systematic linear drift in the offset in time on the order of about 1 $\mu$m/hr. There are two likely causes of this:
colors = sns.color_palette()
xlim=[-19.5, 19]
x_eval = np.linspace(xlim[0], xlim[1], 200)
y_eval, dy_eval = scan_map_2d_1.map.evaluate_model(np.array([x_eval, np.zeros(len(x_eval))]))
plt.plot(x_eval, y_eval, '--', color=colors[2], alpha=.25, label='2d scan fit 1')
plt.fill_between(x_eval, y_eval + dy_eval, y_eval - dy_eval,
alpha=.25, color=sns.color_palette()[2])
scan_map_1d_1.map.plot(fit_expression=False, colors=[colors[0]])
scan_map_1d_2.map.plot(fit_expression=False, colors=[colors[1]])
y_eval, dy_eval = scan_map_2d_2.map.evaluate_model(np.array([x_eval, np.zeros(len(x_eval))]))
plt.plot(x_eval, y_eval, '--', color=colors[3], alpha=.25, label='2d scan fit 2')
plt.fill_between(x_eval, y_eval + dy_eval, y_eval - dy_eval,
alpha=.25, color=sns.color_palette()[3])
_=plt.legend(loc='best')
_=plt.title('1d interferometer scans along the beamline and comparison to 2d scan prediction')
Looking at the variation in the offset, slope, and curvature in the $x$ direction as a function time, we find that
maps = [scan_map_2d_1, scan_map_1d_1, scan_map_1d_2, scan_map_2d_2]
min_time = np.array([time.mktime(m.scans[0].date.timetuple())/3600.0 for m in maps])
max_time = np.array([time.mktime(m.scans[-1].date.timetuple())/3600.0 for m in maps])
t = (min_time + max_time)/2.0
t = t - min(t)
values = { 's':{'options':['a_{}','a(0)'], 'values':[], 'ylabel':'$\mu$m'},
'$ds/dx$':{'options':['a_{$x$}', 'a(1)'], 'values':[], 'ylabel':'$\mu$m/cm'},
'$d^2s/dx^2$':{'options':['a_{$x$$^{2}$}','a(2)'], 'values':[], 'ylabel':'$\mu$m/cm$^2$'}}
subtable = lambda table, option1, option2 : table[np.logical_or((table['names'] == option1),
(table['names'] == option2))]
get_values = lambda table, options: (float(subtable(table,options[0],options[1])['value']),
float(subtable(table,options[0],options[1])['dvalue']))
counter = 0
for m in maps:
table = m.map.get_coefficients()
for k in values.keys():
counter += 1
values[k]['values'].append(get_values(table, values[k]['options']))
N = len(values.keys())
regrs = []
for i, k in enumerate(values.keys()):
ax = plt.subplot(N,1,i+1)
y, dy = zip(*values[k]['values'])
regr = PolynomialFit1D(t, y, dy, order=2,
x_name='t',x_unit='hr',
y_name=k, y_unit=values[k]['ylabel'])
regr.plot(ax=ax, legend=False, fit_expression=False)
regrs.append(regr)
plt.tight_layout()
After having seen this relaxation in either the interferometer or the field plates, I wanted to isolate the source, so I performed a new experiment. In this experiment, I continuously jumped between the null interferometer peak and the plate separation peak, while holding the interferometer at the origin at the center of the field plates. This way, I could measure simultaneously the null peak position and the field plate separation. I did this overnight, but in the middle of the night it failed for a while and i had to restart it.
files = ['./data/2016-03-45mmFPSpacing/2016_03_23_Monitor_Plate_Relaxation-Interferometer Scan 3 23 2016.txt',
'./data/2016-03-45mmFPSpacing/2016_03_24_Monitor_Plate_Relaxation-Interferometer Scan 3 24 2016.txt']
scan_relaxation = InterferometerMap.create_from_txt(files, auto_fit=False)
target = scan_relaxation.setpoint
t = np.array([scan.date for scan in scan_relaxation.scans])
s = np.array([scan.gaussian_fit.center for scan in scan_relaxation.scans])
ds = np.array([scan.gaussian_fit.dcenter for scan in scan_relaxation.scans])
t = np.reshape( t[:2 * (len(t)//2)], [len(t)//2, 2])
s = np.reshape( s[:2 * (len(s)//2)], [len(s)//2, 2])
ds = np.reshape(ds[:2 * (len(ds)//2)],[len(ds)//2, 2])
center_peak = s[:, 1]
dcenter_peak = ds[:, 1]
t = t[:, 0]
s = s[:, 1] - s[:, 0] - target
ds = np.sqrt(ds[:, 1]**2 + ds[:, 0]**2)
tn = np.array([time.mktime(t0.timetuple()) for t0 in t])
tn = (tn - min(tn))/3600
method = 'quadratic'
width = .25
regr0 = KernelEstimator1D(tn, s, ds, width=width, method=method,
x_name='$t$', x_unit='$\\mathrm{hrs}$',
y_name='$s-45000$', y_unit='$\mu\\mathrm{m}$',
label='drift in plate spacing',
renormalize_error=True)
regr0.remove_outliers()
regr1 = KernelEstimator1D(tn, center_peak, dcenter_peak, width=width, method=method,
x_name='$t$', x_unit='$\\mathrm{hrs}$',
y_name='center peak', y_unit='$\mu\\mathrm{m}$',
label='drift in center interferometer peak',
renormalize_error=True)
regr1.remove_outliers()
plt.subplot(2,1,1)
regr0.plot(legend=False)
plt.title('drift in the field plate separation at x=0, y=0')
plt.subplot(2,1,2)
regr1.plot(legend=False)
plt.title('drift in null peak position')
plt.tight_layout()
The results of this experiment show that the null peak is drifting fairly linearly at a rate of about 4 $\mu$m/10 hrs which is comparable to, but smaller than the ~1 $\mu$m/hr drift previously seem. It also shows that the field plate separation is drifting and the maximum slope in the drift is around 5-10 $\mu$m/hour. So the source of the previously seen drift could have been either interferometer creep or field plate creep - we cannot distinguish between the two completely. However, we did measure nonzero drift on comparable timescales for both optical components.
The drift in the null peak position is probably due to histeresis when moving the translation stage over long distances (like that required to measure the separation between the null peak and the separation peak). Previously, we did a characterization of the null peak while just sitting on the null peak rather than jumping back and forth and it stays very constant.
Hence, I believe that the drift in the previous scans was probably due to drift in the field plate separation, not the null peak.
Here is that previous data, in addition to a comparison of the results of different fitting methods.
Image('./photos/Previous_Null_Peak_Analysis.jpg')
db = DatabaseAccess()
ts = db.get_data(channel=['G14 Room Temperature South',
'G14 Room Temperature North'],
start_time=scan_relaxation.scans[0].date,
end_time=scan_relaxation.scans[-1].date, kernel_regression=True)
ts.plot()
It looks like the timescales for the fluctuations are similar, and there are possible correlations between the temperature and field plate separation lending credibility to the hypothesis that these fluctuations are due to thermal expansion of the optics.
# In order to compare these two time series, they need to be sampled
# at the same time points. We don't have that data, though we can estimate
# that data using the KernelEstimate1D models
y_ts, dy_ts = ts[0].regr.evaluate_model(regr0.data.x[0,:])
# perform a correlation analysis
r, dr = correlation_coefficient(regr0.data.y, y_ts,
regr0.data.dy, dy_ts)
p = normal_n_sigma_to_p(np.abs(r/dr))
# create a correlation plot
_=sns.jointplot(regr0.data.y, y_ts,
kind='kde').set_axis_labels('s - 45000 ($\mu$m)',
'G14 Room Temperature North (C)')
# display the correlation analysis result
print('correlation coefficient accounting for errorbars: '
+ scientific_string(r, dr, style='plain') + ', p=' + str(p))
We can derive the following safety margins - how close the closest ballistic trajectory can get to hitting the field plates or the guard rings - given the geometry. Here we assume the following geometric factors:
source_diameter = 7 # mm
dsource_position = 1 # mm
target_collimator_size = 24 # mm
actual_collimator_size = 23.6 # mm
collimator_offset = .4 # mm
dcollimator_offset = .2 # mm
field_plate_spacing = 45 # mm
field_plate_guard_ring_offset = 2.29 # mm
source_to_collimator = 1.1 # m
collimator_to_fp_end = .497 # m
def safety_offset(ss, dss, sc, dsc, l1, l2, ref):
"""
ss: source size (diameter)
dss: source position offset
sc: collimator size (diameter)
dscs: colimator position offset
l1: distance from source to collimator
l2: distance from collimator to end of the guard rings
ref: position of guard rings
"""
return ref - np.abs(((sc/2.0 + dsc) + (((sc/2.0 + dsc) - (-ss/2.0 + dss))) * (l2 / l1)))
def sample_safety_factor(ss, dss, ddss,
sc, dsc, ddsc,
l1, l2, ref,
N=10000):
ddss_sampled = ddss * np.random.normal(size=(N,))
ddsc_sampled = ddsc * np.random.normal(size=(N,))
result = safety_offset(ss, dss + ddss_sampled,
sc, dsc + ddsc_sampled,
l1, l2,
ref)
print np.mean(result), '+/-', np.std(result)
return
1 Distance between closest trajectory and the field plates for perfect alignment:
safety_offset(source_diameter, 0, target_collimator_size, 0,
source_to_collimator, collimator_to_fp_end,
field_plate_spacing/2.0)
2 Distance between closest trajectory and the field plates with measured collimator offset and collimator size
safety_offset(source_diameter, 0, actual_collimator_size, collimator_offset,
source_to_collimator, collimator_to_fp_end,
field_plate_spacing/2.0)
3 worst case closest trajectory to the field plates with measured collimator offset and collimator size and estimated uncertainty in the collimator offset, and estimated uncertainty in beam source alignment.
safety_offset(source_diameter, -dsource_position, actual_collimator_size, collimator_offset + dcollimator_offset,
source_to_collimator, collimator_to_fp_end,
field_plate_spacing/2.0)
4 assuming uncorrelated random variables, the typical closest trajectory to the field plates with measured collimator offset and collimator size and estimated uncertainty in the collimator offset, and estimated uncertainty in beam source alignment will be:
sample_safety_factor(source_diameter,0,dsource_position,
actual_collimator_size,collimator_offset,dcollimator_offset,
source_to_collimator, collimator_to_fp_end,
field_plate_spacing/2.0)
5 Distance between closest trajectory and the guard rings for perfect alignment:
safety_offset(source_diameter, 0, target_collimator_size, 0,
source_to_collimator, collimator_to_fp_end,
field_plate_spacing/2.0 - field_plate_guard_ring_offset)
6 Distance between closest trajectory and the guard rings with measured collimator offset and collimator size
safety_offset(source_diameter, 0, actual_collimator_size, collimator_offset,
source_to_collimator, collimator_to_fp_end,
field_plate_spacing/2.0 - field_plate_guard_ring_offset)
7 worst case closest trajectory to the guard rings with measured collimator offset and collimator size and estimated uncertainty in the collimator offset, and estimated uncertainty in beam source alignment.
safety_offset(source_diameter, -dsource_position, actual_collimator_size, collimator_offset + dcollimator_offset,
source_to_collimator, collimator_to_fp_end,
field_plate_spacing/2.0 - field_plate_guard_ring_offset)
8 assuming uncorrelated random variables, the typical closest trajectory to the guard rings with measured collimator offset and collimator size and estimated uncertainty in the collimator offset, and estimated uncertainty in beam source alignment will be:
sample_safety_factor(source_diameter,0,dsource_position,
actual_collimator_size,collimator_offset,dcollimator_offset,
source_to_collimator, collimator_to_fp_end,
field_plate_spacing/2.0 - field_plate_guard_ring_offset)
ts1 = db.get_data(channel='Dump Region Pressure',
start_time=datetime.datetime(2016,3,25,19,0),
duration='2.5 days')
ts1.plot()
plt.yscale('log')
print('final pressure: ' + str(ts[0].x[-1]) + ' (torr)')
Here is the mass spectrum and the gas make-up in the interaction region after baking:
sys.path.append('../MassSpectra/code')
from mass_spectra import MassSpectrumLibrary, MassSpectrum
This spectrum shows possible components in the make-up of the vacuum chamber. Mass spectrum analysis is an analysis of molecule fragments, and for that reason, many large molecules show similar fingerprints of other large molecules and subsets of those molecules.
Among those compounds that the algorithm has picked out as possibly being represented in the spectrum, I generally only believe the assignment if there is a plausible reason why that compound might be present in our chamber. In this case, I believe we have:
lib = MassSpectrumLibrary('../MassSpectra/library/') # import my mass spectrum library
# here is the last mass spectrum that we took after having baked.
ms = MassSpectrum('../MassSpectra/data/After Bakeout After RGA Relocation 3-21-2016 With Electron Multiplier.xml',
library=lib, fit_method='one', renormalize_error=False)
plt.figure(figsize=(15,8))
ms.print_report()
Here are the largest peaks and their peak assignments
ms.peak_assignments().sort_values(by='pressure (torr)', ascending=False).head(10)